// Decompiled on 周六 2月 22 19:31:49 CST 2025 with Zomboid Decompiler v0.1.3 using Vineflower.
package zombie.savefile;

import gnu.trove.set.hash.TIntHashSet;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;
import zombie.characters.IsoPlayer;
import zombie.core.Core;
import zombie.core.logger.ExceptionLogger;
import zombie.core.utils.UpdateLimit;
import zombie.debug.DebugLog;
import zombie.iso.IsoCell;
import zombie.iso.IsoChunkMap;
import zombie.iso.IsoWorld;
import zombie.iso.WorldStreamer;
import zombie.util.ByteBufferBackedInputStream;
import zombie.util.ByteBufferOutputStream;
import zombie.vehicles.VehiclesDB2;

public final class PlayerDB {
    public static final int INVALID_ID = -1;
    private static final int MIN_ID = 1;
    private static PlayerDB instance = null;
    private static final ThreadLocal<ByteBuffer> TL_SliceBuffer = ThreadLocal.withInitial(() -> ByteBuffer.allocate(32768));
    private static final ThreadLocal<byte[]> TL_Bytes = ThreadLocal.withInitial(() -> new byte[1024]);
    private static boolean s_allow = false;
    private final PlayerDB.IPlayerStore m_store = new PlayerDB.SQLPlayerStore();
    private final TIntHashSet m_usedIDs = new TIntHashSet();
    private final ConcurrentLinkedQueue<PlayerDB.PlayerData> m_toThread = new ConcurrentLinkedQueue();
    private final ConcurrentLinkedQueue<PlayerDB.PlayerData> m_fromThread = new ConcurrentLinkedQueue();
    private boolean m_forceSavePlayers;
    public boolean m_canSavePlayers = false;
    private final UpdateLimit m_saveToDBPeriod = new UpdateLimit(10000L);

    public static synchronized PlayerDB getInstance() {
        if (instance == null && s_allow) {
            instance = new PlayerDB();
        }

        return instance;
    }

    public static void setAllow(boolean en) {
        s_allow = en;
    }

    public static boolean isAllow() {
        return s_allow;
    }

    public static boolean isAvailable() {
        return instance != null;
    }

    public PlayerDB() {
        if (!Core.getInstance().isNoSave()) {
            this.create();
        }
    }

    private void create() {
        try {
            this.m_store.init(this.m_usedIDs);
            this.m_usedIDs.add(1);
        } catch (Exception exception) {
            ExceptionLogger.logException(exception);
        }
    }

    public void close() {
        assert WorldStreamer.instance.worldStreamer == null;

        this.updateWorldStreamer();

        assert this.m_toThread.isEmpty();

        try {
            this.m_store.Reset();
        } catch (Exception exception) {
            ExceptionLogger.logException(exception);
        }

        this.m_fromThread.clear();
        instance = null;
        s_allow = false;
    }

    private int allocateID() {
        synchronized (this.m_usedIDs) {
            for (int _int = 1; _int < Integer.MAX_VALUE; _int++) {
                if (!this.m_usedIDs.contains(_int)) {
                    this.m_usedIDs.add(_int);
                    return _int;
                }
            }
        }

        throw new RuntimeException("ran out of unused players.db ids");
    }

    private PlayerDB.PlayerData allocPlayerData() {
        PlayerDB.PlayerData playerData = (PlayerDB.PlayerData)this.m_fromThread.poll();
        if (playerData == null) {
            playerData = new PlayerDB.PlayerData();
        }

        assert playerData.m_sqlID == -1;

        return playerData;
    }

    private void releasePlayerData(PlayerDB.PlayerData playerData) {
        playerData.m_sqlID = -1;
        this.m_fromThread.add(playerData);
    }

    public void updateMain() {
        if (this.m_canSavePlayers && (this.m_forceSavePlayers || this.m_saveToDBPeriod.Check())) {
            this.m_forceSavePlayers = false;
            this.savePlayersAsync();
            VehiclesDB2.instance.setForceSave();
        }
    }

    public void updateWorldStreamer() {
        for (PlayerDB.PlayerData playerData = (PlayerDB.PlayerData)this.m_toThread.poll();
            playerData != null;
            playerData = (PlayerDB.PlayerData)this.m_toThread.poll()
        ) {
            try {
                this.m_store.save(playerData);
            } catch (Exception exception) {
                ExceptionLogger.logException(exception);
            } finally {
                this.releasePlayerData(playerData);
            }
        }
    }

    private void savePlayerAsync(IsoPlayer isoPlayer) throws Exception {
        if (isoPlayer != null) {
            if (isoPlayer.sqlID == -1) {
                isoPlayer.sqlID = this.allocateID();
            }

            PlayerDB.PlayerData playerData = this.allocPlayerData();

            try {
                playerData.set(isoPlayer);
                this.m_toThread.add(playerData);
            } catch (Exception exception) {
                this.releasePlayerData(playerData);
                throw exception;
            }
        }
    }

    private void savePlayersAsync() {
        for (int _int = 0; _int < IsoPlayer.numPlayers; _int++) {
            IsoPlayer isoPlayer = IsoPlayer.players[_int];
            if (isoPlayer != null) {
                try {
                    this.savePlayerAsync(isoPlayer);
                } catch (Exception exception) {
                    ExceptionLogger.logException(exception);
                }
            }
        }
    }

    public void savePlayers() {
        if (this.m_canSavePlayers) {
            this.m_forceSavePlayers = true;
        }
    }

    public void saveLocalPlayersForce() {
        this.savePlayersAsync();
        if (WorldStreamer.instance.worldStreamer == null) {
            this.updateWorldStreamer();
        }
    }

    public void importPlayersFromVehiclesDB() {
        VehiclesDB2.instance.importPlayersFromOldDB((var1, string, var3, var4, float0, float1, float2, _int, _byte, _boolean) -> {
            PlayerDB.PlayerData playerData = this.allocPlayerData();
            playerData.m_sqlID = this.allocateID();
            playerData.m_x = float0;
            playerData.m_y = float1;
            playerData.m_z = float2;
            playerData.m_isDead = _boolean;
            playerData.m_name = string;
            playerData.m_WorldVersion = _int;
            playerData.setBytes(_byte);

            try {
                this.m_store.save(playerData);
            } catch (Exception exception) {
                ExceptionLogger.logException(exception);
            }

            this.releasePlayerData(playerData);
        });
    }

    public void uploadLocalPlayers2DB() {
        this.savePlayersAsync();
        String string = zombie.ZomboidFileSystem.instance.getCurrentSaveDir();

        for (int _int = 1; _int < 100; _int++) {
            File file = new File(string + File.separator + "map_p" + _int + ".bin");
            if (file.exists()) {
                try {
                    IsoPlayer isoPlayer = new IsoPlayer(IsoWorld.instance.CurrentCell);
                    isoPlayer.load(file.getAbsolutePath());
                    this.savePlayerAsync(isoPlayer);
                    file.delete();
                } catch (Exception exception) {
                    ExceptionLogger.logException(exception);
                }
            }
        }

        if (WorldStreamer.instance.worldStreamer == null) {
            this.updateWorldStreamer();
        }
    }

    private boolean loadPlayer(int _int, IsoPlayer isoPlayer) {
        PlayerDB.PlayerData playerData = this.allocPlayerData();

        boolean _boolean;
        try {
            playerData.m_sqlID = _int;
            if (this.m_store.load(playerData)) {
                isoPlayer.load(playerData.m_byteBuffer, playerData.m_WorldVersion);
                if (playerData.m_isDead) {
                    isoPlayer.getBodyDamage().setOverallBodyHealth(0.0F);
                    isoPlayer.setHealth(0.0F);
                }

                isoPlayer.sqlID = _int;
                return true;
            }

            _boolean = false;
        } catch (Exception exception) {
            ExceptionLogger.logException(exception);
            return false;
        } finally {
            this.releasePlayerData(playerData);
        }

        return _boolean;
    }

    public boolean loadLocalPlayer(int sqlId) {
        try {
            IsoPlayer isoPlayer = IsoPlayer.getInstance();
            if (isoPlayer == null) {
                isoPlayer = new IsoPlayer(IsoCell.getInstance());
                IsoPlayer.setInstance(isoPlayer);
                IsoPlayer.players[0] = isoPlayer;
            }

            if (this.loadPlayer(sqlId, isoPlayer)) {
                int int0 = (int)(isoPlayer.x / 10.0F);
                int int1 = (int)(isoPlayer.y / 10.0F);
                IsoCell.getInstance().ChunkMap[IsoPlayer.getPlayerIndex()].WorldX = int0 + IsoWorld.saveoffsetx * 30;
                IsoCell.getInstance().ChunkMap[IsoPlayer.getPlayerIndex()].WorldY = int1 + IsoWorld.saveoffsety * 30;
                return true;
            }
        } catch (Exception exception) {
            ExceptionLogger.logException(exception);
        }

        return false;
    }

    public ArrayList<IsoPlayer> getAllLocalPlayers() {
        ArrayList arrayList = new ArrayList();
        this.m_usedIDs.forEach(_int -> {
            if (_int <= 1) {
                return true;
            } else {
                IsoPlayer isoPlayer = new IsoPlayer(IsoWorld.instance.CurrentCell);
                if (this.loadPlayer(_int, isoPlayer)) {
                    arrayList.add(isoPlayer);
                }

                return true;
            }
        });
        return arrayList;
    }

    public boolean loadLocalPlayerInfo(int sqlId) {
        PlayerDB.PlayerData playerData = this.allocPlayerData();

        boolean _boolean;
        try {
            playerData.m_sqlID = sqlId;
            if (!this.m_store.loadEverythingExceptBytes(playerData)) {
                return false;
            }

            IsoChunkMap.WorldXA = (int)playerData.m_x;
            IsoChunkMap.WorldYA = (int)playerData.m_y;
            IsoChunkMap.WorldZA = (int)playerData.m_z;
            IsoChunkMap.WorldXA = IsoChunkMap.WorldXA + 300 * IsoWorld.saveoffsetx;
            IsoChunkMap.WorldYA = IsoChunkMap.WorldYA + 300 * IsoWorld.saveoffsety;
            IsoChunkMap.SWorldX[0] = (int)(playerData.m_x / 10.0F);
            IsoChunkMap.SWorldY[0] = (int)(playerData.m_y / 10.0F);
            IsoChunkMap.SWorldX[0] = IsoChunkMap.SWorldX[0] + 30 * IsoWorld.saveoffsetx;
            IsoChunkMap.SWorldY[0] = IsoChunkMap.SWorldY[0] + 30 * IsoWorld.saveoffsety;
            _boolean = true;
        } catch (Exception exception) {
            ExceptionLogger.logException(exception);
            return false;
        } finally {
            this.releasePlayerData(playerData);
        }

        return _boolean;
    }

    private interface IPlayerStore {
        void init(TIntHashSet var1) throws Exception;

        void Reset() throws Exception;

        void save(PlayerDB.PlayerData var1) throws Exception;

        boolean load(PlayerDB.PlayerData var1) throws Exception;

        boolean loadEverythingExceptBytes(PlayerDB.PlayerData var1) throws Exception;
    }

    private static final class PlayerData {
        int m_sqlID = -1;
        float m_x;
        float m_y;
        float m_z;
        boolean m_isDead;
        String m_name;
        int m_WorldVersion;
        ByteBuffer m_byteBuffer = ByteBuffer.allocate(32768);

        PlayerDB.PlayerData set(IsoPlayer isoPlayer) throws IOException {
            assert isoPlayer.sqlID >= 1;

            this.m_sqlID = isoPlayer.sqlID;
            this.m_x = isoPlayer.getX();
            this.m_y = isoPlayer.getY();
            this.m_z = isoPlayer.getZ();
            this.m_isDead = isoPlayer.isDead();
            this.m_name = isoPlayer.getDescriptor().getForename() + " " + isoPlayer.getDescriptor().getSurname();
            this.m_WorldVersion = IsoWorld.getWorldVersion();
            ByteBuffer byteBuffer = (ByteBuffer)PlayerDB.TL_SliceBuffer.get();
            byteBuffer.clear();

            while (true) {
                try {
                    isoPlayer.save(byteBuffer);
                    break;
                } catch (BufferOverflowException bufferOverflowException) {
                    if (byteBuffer.capacity() >= 2097152) {
                        DebugLog.General.error("the player %s cannot be saved", isoPlayer.getUsername());
                        throw bufferOverflowException;
                    }

                    byteBuffer = ByteBuffer.allocate(byteBuffer.capacity() + 32768);
                    PlayerDB.TL_SliceBuffer.set(byteBuffer);
                }
            }

            byteBuffer.flip();
            this.setBytes(byteBuffer);
            return this;
        }

        void setBytes(ByteBuffer byteBuffer) {
            byteBuffer.rewind();
            ByteBufferOutputStream byteBufferOutputStream = new ByteBufferOutputStream(this.m_byteBuffer, true);
            byteBufferOutputStream.clear();
            byte[] _byte = (byte[])PlayerDB.TL_Bytes.get();
            int int0 = byteBuffer.limit();

            while (int0 > 0) {
                int int1 = Math.min(_byte.length, int0);
                byteBuffer.get(_byte, 0, int1);
                byteBufferOutputStream.write(_byte, 0, int1);
                int0 -= int1;
            }

            byteBufferOutputStream.flip();
            this.m_byteBuffer = byteBufferOutputStream.getWrappedBuffer();
        }

        void setBytes(byte[] _byte) {
            ByteBufferOutputStream byteBufferOutputStream = new ByteBufferOutputStream(this.m_byteBuffer, true);
            byteBufferOutputStream.clear();
            byteBufferOutputStream.write(_byte);
            byteBufferOutputStream.flip();
            this.m_byteBuffer = byteBufferOutputStream.getWrappedBuffer();
        }

        void setBytes(InputStream inputStream) throws IOException {
            ByteBufferOutputStream byteBufferOutputStream = new ByteBufferOutputStream(this.m_byteBuffer, true);
            byteBufferOutputStream.clear();
            byte[] _byte = (byte[])PlayerDB.TL_Bytes.get();

            while (true) {
                int _int = inputStream.read(_byte);
                if (_int < 1) {
                    byteBufferOutputStream.flip();
                    this.m_byteBuffer = byteBufferOutputStream.getWrappedBuffer();
                    return;
                }

                byteBufferOutputStream.write(_byte, 0, _int);
            }
        }
    }

    private static final class SQLPlayerStore implements PlayerDB.IPlayerStore {
        Connection m_conn = null;

        @Override
        public void init(TIntHashSet tIntHashSet) throws Exception {
            tIntHashSet.clear();
            if (!Core.getInstance().isNoSave()) {
                this.m_conn = PlayerDBHelper.create();
                this.initUsedIDs(tIntHashSet);
            }
        }

        @Override
        public void Reset() {
            if (this.m_conn != null) {
                try {
                    this.m_conn.close();
                } catch (SQLException sQLException) {
                    ExceptionLogger.logException(sQLException);
                }

                this.m_conn = null;
            }
        }

        @Override
        public void save(PlayerDB.PlayerData playerData) throws Exception {
            assert playerData.m_sqlID >= 1;

            if (this.m_conn != null) {
                if (this.isInDB(playerData.m_sqlID)) {
                    this.update(playerData);
                } else {
                    this.add(playerData);
                }
            }
        }

        @Override
        public boolean load(PlayerDB.PlayerData playerData) throws Exception {
            assert playerData.m_sqlID >= 1;

            if (this.m_conn == null) {
                return false;
            } else {
                String string = "SELECT data,worldversion,x,y,z,isDead,name FROM localPlayers WHERE id=?";
                PreparedStatement preparedStatement = this.m_conn.prepareStatement(string);

                boolean _boolean;
                label50: {
                    try {
                        preparedStatement.setInt(1, playerData.m_sqlID);
                        ResultSet resultSet = preparedStatement.executeQuery();
                        if (resultSet.next()) {
                            InputStream inputStream = resultSet.getBinaryStream(1);
                            playerData.setBytes(inputStream);
                            playerData.m_WorldVersion = resultSet.getInt(2);
                            playerData.m_x = (float)resultSet.getInt(3);
                            playerData.m_y = (float)resultSet.getInt(4);
                            playerData.m_z = (float)resultSet.getInt(5);
                            playerData.m_isDead = resultSet.getBoolean(6);
                            playerData.m_name = resultSet.getString(7);
                            _boolean = true;
                            break label50;
                        }
                    } catch (Throwable throwable0) {
                        if (preparedStatement != null) {
                            try {
                                preparedStatement.close();
                            } catch (Throwable throwable1) {
                                throwable0.addSuppressed(throwable1);
                            }
                        }

                        throw throwable0;
                    }

                    if (preparedStatement != null) {
                        preparedStatement.close();
                    }

                    return false;
                }

                if (preparedStatement != null) {
                    preparedStatement.close();
                }

                return _boolean;
            }
        }

        @Override
        public boolean loadEverythingExceptBytes(PlayerDB.PlayerData playerData) throws Exception {
            if (this.m_conn == null) {
                return false;
            } else {
                String string = "SELECT worldversion,x,y,z,isDead,name FROM localPlayers WHERE id=?";
                PreparedStatement preparedStatement = this.m_conn.prepareStatement(string);

                boolean _boolean;
                label44: {
                    try {
                        preparedStatement.setInt(1, playerData.m_sqlID);
                        ResultSet resultSet = preparedStatement.executeQuery();
                        if (resultSet.next()) {
                            playerData.m_WorldVersion = resultSet.getInt(1);
                            playerData.m_x = (float)resultSet.getInt(2);
                            playerData.m_y = (float)resultSet.getInt(3);
                            playerData.m_z = (float)resultSet.getInt(4);
                            playerData.m_isDead = resultSet.getBoolean(5);
                            playerData.m_name = resultSet.getString(6);
                            _boolean = true;
                            break label44;
                        }
                    } catch (Throwable throwable0) {
                        if (preparedStatement != null) {
                            try {
                                preparedStatement.close();
                            } catch (Throwable throwable1) {
                                throwable0.addSuppressed(throwable1);
                            }
                        }

                        throw throwable0;
                    }

                    if (preparedStatement != null) {
                        preparedStatement.close();
                    }

                    return false;
                }

                if (preparedStatement != null) {
                    preparedStatement.close();
                }

                return _boolean;
            }
        }

        void initUsedIDs(TIntHashSet tIntHashSet) throws SQLException {
            String string = "SELECT id FROM localPlayers";
            PreparedStatement preparedStatement = this.m_conn.prepareStatement(string);

            try {
                ResultSet resultSet = preparedStatement.executeQuery();

                while (resultSet.next()) {
                    tIntHashSet.add(resultSet.getInt(1));
                }
            } catch (Throwable throwable0) {
                if (preparedStatement != null) {
                    try {
                        preparedStatement.close();
                    } catch (Throwable throwable1) {
                        throwable0.addSuppressed(throwable1);
                    }
                }

                throw throwable0;
            }

            if (preparedStatement != null) {
                preparedStatement.close();
            }
        }

        boolean isInDB(int _int) throws SQLException {
            String string = "SELECT 1 FROM localPlayers WHERE id=?";
            PreparedStatement preparedStatement = this.m_conn.prepareStatement(string);

            boolean _boolean;
            try {
                preparedStatement.setInt(1, _int);
                ResultSet resultSet = preparedStatement.executeQuery();
                _boolean = resultSet.next();
            } catch (Throwable throwable0) {
                if (preparedStatement != null) {
                    try {
                        preparedStatement.close();
                    } catch (Throwable throwable1) {
                        throwable0.addSuppressed(throwable1);
                    }
                }

                throw throwable0;
            }

            if (preparedStatement != null) {
                preparedStatement.close();
            }

            return _boolean;
        }

        void add(PlayerDB.PlayerData playerData) throws Exception {
            if (this.m_conn != null && playerData.m_sqlID >= 1) {
                String string = "INSERT INTO localPlayers(wx,wy,x,y,z,worldversion,data,isDead,name,id) VALUES(?,?,?,?,?,?,?,?,?,?)";

                try {
                    PreparedStatement preparedStatement = this.m_conn.prepareStatement(string);

                    try {
                        preparedStatement.setInt(1, (int)(playerData.m_x / 10.0F));
                        preparedStatement.setInt(2, (int)(playerData.m_y / 10.0F));
                        preparedStatement.setFloat(3, playerData.m_x);
                        preparedStatement.setFloat(4, playerData.m_y);
                        preparedStatement.setFloat(5, playerData.m_z);
                        preparedStatement.setInt(6, playerData.m_WorldVersion);
                        ByteBuffer byteBuffer = playerData.m_byteBuffer;
                        byteBuffer.rewind();
                        preparedStatement.setBinaryStream(7, new ByteBufferBackedInputStream(byteBuffer), byteBuffer.remaining());
                        preparedStatement.setBoolean(8, playerData.m_isDead);
                        preparedStatement.setString(9, playerData.m_name);
                        preparedStatement.setInt(10, playerData.m_sqlID);
                        int _int = preparedStatement.executeUpdate();
                        this.m_conn.commit();
                    } catch (Throwable throwable0) {
                        if (preparedStatement != null) {
                            try {
                                preparedStatement.close();
                            } catch (Throwable throwable1) {
                                throwable0.addSuppressed(throwable1);
                            }
                        }

                        throw throwable0;
                    }

                    if (preparedStatement != null) {
                        preparedStatement.close();
                    }
                } catch (Exception exception) {
                    PlayerDBHelper.rollback(this.m_conn);
                    throw exception;
                }
            }
        }

        public void update(PlayerDB.PlayerData playerData) throws Exception {
            if (this.m_conn != null && playerData.m_sqlID >= 1) {
                String string = "UPDATE localPlayers SET wx = ?, wy = ?, x = ?, y = ?, z = ?, worldversion = ?, data = ?, isDead = ?, name = ? WHERE id=?";

                try {
                    PreparedStatement preparedStatement = this.m_conn.prepareStatement(string);

                    try {
                        preparedStatement.setInt(1, (int)(playerData.m_x / 10.0F));
                        preparedStatement.setInt(2, (int)(playerData.m_y / 10.0F));
                        preparedStatement.setFloat(3, playerData.m_x);
                        preparedStatement.setFloat(4, playerData.m_y);
                        preparedStatement.setFloat(5, playerData.m_z);
                        preparedStatement.setInt(6, playerData.m_WorldVersion);
                        ByteBuffer byteBuffer = playerData.m_byteBuffer;
                        byteBuffer.rewind();
                        preparedStatement.setBinaryStream(7, new ByteBufferBackedInputStream(byteBuffer), byteBuffer.remaining());
                        preparedStatement.setBoolean(8, playerData.m_isDead);
                        preparedStatement.setString(9, playerData.m_name);
                        preparedStatement.setInt(10, playerData.m_sqlID);
                        int _int = preparedStatement.executeUpdate();
                        this.m_conn.commit();
                    } catch (Throwable throwable0) {
                        if (preparedStatement != null) {
                            try {
                                preparedStatement.close();
                            } catch (Throwable throwable1) {
                                throwable0.addSuppressed(throwable1);
                            }
                        }

                        throw throwable0;
                    }

                    if (preparedStatement != null) {
                        preparedStatement.close();
                    }
                } catch (Exception exception) {
                    PlayerDBHelper.rollback(this.m_conn);
                    throw exception;
                }
            }
        }
    }
}
